import chai, { expect } from 'chai';
import { Helper } from '@teambit/legacy.e2e-helper';
import chaiFs from 'chai-fs';
chai.use(chaiFs);

describe('ci commands', function () {
  this.timeout(0);
  let helper: Helper;

  before(() => {
    helper = new Helper();
  });

  after(() => {
    helper.scopeHelper.destroy();
  });

  function setupGitRemote() {
    // Create a bare git repository to serve as remote
    const { scopePath } = helper.scopeHelper.getNewBareScope();
    const bareRepoPath = scopePath.replace('.bit', '.git');
    helper.command.runCmd(`git init --bare ${bareRepoPath}`);

    // Initialize git in workspace and set up remote
    helper.git.initNewGitRepo(true);
    helper.command.runCmd(`git remote add origin ${bareRepoPath}`);

    return bareRepoPath;
  }

  function setupComponentsAndInitialCommit(numComponents = 2) {
    // Create components and initial export
    helper.fixtures.populateComponents(numComponents);
    helper.command.tagAllWithoutBuild();
    helper.command.export();

    // Create .gitignore file
    helper.fs.outputFile('.gitignore', 'node_modules/\n.bit/\n');

    // Initial git commit and push to remote
    helper.command.runCmd('git add .');
    helper.command.runCmd('git commit -m "initial commit"');

    // Get the current branch name (could be main or master depending on git version)
    const currentBranch = helper.command.runCmd('git branch --show-current').trim();
    helper.command.runCmd(`git push -u origin ${currentBranch}`);

    // Store the default branch name for later use
    return currentBranch;
  }

  describe('bit ci pr workflow', () => {
    let prOutput: string;
    before(() => {
      helper.scopeHelper.setWorkspaceWithRemoteScope();
      setupGitRemote();
      setupComponentsAndInitialCommit();

      // Create a feature branch
      helper.command.runCmd('git checkout -b feature/test-pr');

      // Make some changes
      helper.fs.outputFile('comp1/comp1.js', 'console.log("updated component");');
      helper.command.runCmd('git add comp1/comp1.js');
      helper.command.runCmd('git commit -m "feat: update component"');

      // in real world scenario, you would push the branch to the remote
      // and this "bit ci" command would be run in a CI environment

      prOutput = helper.command.runCmd('bit ci pr --message "test pr message"');
    });
    it('should complete successfully', () => {
      expect(prOutput).to.include('PR command executed successfully');
    });
    it('should create a lane with the components and switch back to main', () => {
      const lanes = helper.command.listLanesParsed();
      expect(lanes.currentLane).to.equal('main');
      expect(lanes.lanes).to.have.lengthOf(2);
      expect(lanes.lanes[0].name).to.equal('feature-test-pr');
      expect(lanes.lanes[0].components).to.have.lengthOf(1);
    });
    it('should export the lane to the remote', () => {
      const remoteLanes = helper.command.listRemoteLanesParsed();
      expect(remoteLanes.lanes[0].name).to.equal('feature-test-pr');
      expect(remoteLanes.lanes[0].components).to.have.lengthOf(1);
    });
    describe('importing the lane to a new workspace', () => {
      before(() => {
        helper.scopeHelper.reInitWorkspace();
        helper.scopeHelper.addRemoteScope();
        helper.command.importLane(`feature-test-pr`, '-x');
      });
      it('should import the lane successfully', () => {
        const lanes = helper.command.listLanesParsed();
        expect(lanes.currentLane).to.equal(`feature-test-pr`);
      });
      it('should save the git message into the snap message', () => {
        const log = helper.command.logParsed('comp1');
        const lastLog = log[log.length - 1];
        expect(lastLog.message).to.include('test pr message');
      });
    });
  });

  describe('bit ci merge workflow', () => {
    let mergeOutput: string;
    before(() => {
      helper.scopeHelper.setWorkspaceWithRemoteScope();
      setupGitRemote();
      const defaultBranch = setupComponentsAndInitialCommit();

      // Create feature branch and make changes
      helper.command.runCmd('git checkout -b feature/test-merge');
      helper.fs.outputFile('comp1/comp1.js', 'console.log("merge test");');
      helper.command.runCmd('git add comp1/comp1.js');
      helper.command.runCmd('git commit -m "fix: component update for merge"');

      // Simulate PR merge scenario by going back to default branch
      helper.command.runCmd(`git checkout ${defaultBranch}`);
      helper.command.runCmd('git merge feature/test-merge');

      // Run bit ci merge command
      mergeOutput = helper.command.runCmd('bit ci merge --message "test merge message"');
    });
    it('should complete successfully', () => {
      expect(mergeOutput).to.include('Merged PR');
    });
    it('should tag the component', () => {
      const list = helper.command.listParsed();
      const comp1 = list.find((comp) => comp.id.includes('comp1'));
      expect(comp1).to.exist;
      expect(comp1?.currentVersion).to.equal('0.0.2');
    });
    it('status should be clean', () => {
      const status = helper.command.statusJson();
      expect(status.modifiedComponents).to.have.lengthOf(0);
      helper.command.expectStatusToBeClean();
    });
    it('should export tagged components to remote', () => {
      const list = helper.command.listRemoteScopeParsed();
      const comp1 = list.find((comp) => comp.id.includes('comp1'));
      expect(comp1?.localVersion).to.equal('0.0.2');
    });
    it('should save the "ci pr" message into the tag message', () => {
      const log = helper.command.logParsed('comp1');
      const lastLog = log[log.length - 1];
      expect(lastLog.message).to.include('test merge message');
    });
  });

  describe('multi-workspace scenario', () => {
    let prOutput: string;
    before(() => {
      // Original workspace setup with git remote
      setupComponentsAndInitialCommit(1);

      // Create and push feature branch
      helper.command.runCmd('git checkout -b feature/multi-workspace');
      helper.fs.outputFile('comp1/comp1.js', 'console.log("multi workspace test");');
      helper.command.runCmd('git add comp1/comp1.js');
      helper.command.runCmd('git commit -m "feat: multi workspace changes"');
      helper.command.runCmd('git push -u origin feature/multi-workspace');

      // Simulate cloning to new workspace using git helpers
      helper.git.mimicGitCloneLocalProjectHarmony();
      helper.scopeHelper.addRemoteScope();

      // Set up git remote in the new workspace
      helper.command.runCmd('git fetch origin');
      helper.command.runCmd('git checkout feature/multi-workspace');

      // Run ci pr command in the "cloned" workspace
      prOutput = helper.command.runCmd(`bit ci pr --lane ${helper.scopes.remote}/test-clone-lane`);
    });
    it('should complete successfully', () => {
      expect(prOutput).to.include('PR command executed successfully');
    });
    it('should create the specified lane', () => {
      const lanes = helper.command.listLanesParsed();
      expect(lanes.currentLane).to.equal('main');
      expect(lanes.lanes).to.have.lengthOf(2);
      expect(lanes.lanes[0].name).to.equal('test-clone-lane');
      expect(lanes.lanes[0].components).to.have.lengthOf(1);
    });
    it('should export the lane to the remote', () => {
      const remoteLanes = helper.command.listRemoteLanesParsed();
      expect(remoteLanes.lanes[0].name).to.equal('test-clone-lane');
      expect(remoteLanes.lanes[0].components).to.have.lengthOf(1);
    });
  });

  describe('bit ci merge with versions file', () => {
    let mergeOutput: string;
    before(() => {
      helper.scopeHelper.setWorkspaceWithRemoteScope();
      setupGitRemote();
      const defaultBranch = setupComponentsAndInitialCommit(3);

      // Create feature branch and make changes
      helper.command.runCmd('git checkout -b feature/test-versions-file');
      helper.fs.outputFile('comp1/comp1.js', 'console.log("versions file test");');
      helper.fs.outputFile('comp2/comp2.js', 'console.log("versions file test 2");');
      helper.fs.outputFile('comp3/comp3.js', 'console.log("versions file test 3");');
      helper.command.runCmd('git add .');
      helper.command.runCmd('git commit -m "feat: update components for versions file test"');

      // Simulate PR merge scenario by going back to default branch
      helper.command.runCmd(`git checkout ${defaultBranch}`);
      helper.command.runCmd('git merge feature/test-versions-file');

      // Create versions file
      const versionsFileContent = `# Default version for unspecified components
DEFAULT: minor

# Component-specific versions
${helper.scopes.remote}/comp1: 2.0.0
${helper.scopes.remote}/comp3: 1.5.0`;
      helper.fs.outputFile('versions.txt', versionsFileContent);

      // Add the versions file to git so it survives the git operations in ci merge
      helper.command.runCmd('git add versions.txt');
      helper.command.runCmd('git commit -m "add versions file"');

      // Run bit ci merge command with versions file (use same approach as working tag test)
      mergeOutput = helper.command.runCmd('bit ci merge --versions-file versions.txt');
    });
    it('should complete successfully', () => {
      expect(mergeOutput).to.include('Merged PR');
    });
    it('should tag components according to the versions file', () => {
      const list = helper.command.listParsed();
      const comp1 = list.find((comp) => comp.id.includes('comp1'));
      const comp2 = list.find((comp) => comp.id.includes('comp2'));
      const comp3 = list.find((comp) => comp.id.includes('comp3'));

      expect(comp1?.currentVersion).to.equal('2.0.0'); // specific version from file
      expect(comp2?.currentVersion).to.equal('0.1.0'); // default version (minor) from file
      expect(comp3?.currentVersion).to.equal('1.5.0'); // specific version from file
    });
    it('status should be clean', () => {
      const status = helper.command.statusJson();
      expect(status.modifiedComponents).to.have.lengthOf(0);
      helper.command.expectStatusToBeClean();
    });
    it('should export tagged components to remote', () => {
      const list = helper.command.listRemoteScopeParsed();
      const comp1 = list.find((comp) => comp.id.includes('comp1'));
      const comp2 = list.find((comp) => comp.id.includes('comp2'));
      const comp3 = list.find((comp) => comp.id.includes('comp3'));

      expect(comp1?.localVersion).to.equal('2.0.0');
      expect(comp2?.localVersion).to.equal('0.1.0');
      expect(comp3?.localVersion).to.equal('1.5.0');
    });
  });

  describe('bit ci merge when checked out to a lane', () => {
    let mergeOutput: string;
    before(() => {
      helper.scopeHelper.setWorkspaceWithRemoteScope();
      setupGitRemote();
      const defaultBranch = setupComponentsAndInitialCommit();

      helper.fs.outputFile('comp1/comp1.js', 'console.log("merge test");');
      helper.command.createLane('test-merge-lane');
      helper.command.snapAllComponentsWithoutBuild();
      helper.command.export();

      helper.command.runCmd('git checkout -b feature/test-merge');
      helper.command.runCmd('git add .');
      helper.command.runCmd('git commit -m "fix: component update for merge"');

      // Simulate PR merge scenario by going back to default branch
      helper.command.runCmd(`git checkout ${defaultBranch}`);
      helper.command.runCmd('git merge feature/test-merge');

      // Run bit ci merge command
      mergeOutput = helper.command.runCmd('bit ci merge');
    });
    it('should complete successfully', () => {
      expect(mergeOutput).to.include('Merged PR');
    });
    it('should tag the component', () => {
      const list = helper.command.listParsed();
      const comp1 = list.find((comp) => comp.id.includes('comp1'));
      expect(comp1).to.exist;
      expect(comp1?.currentVersion).to.equal('0.0.2');
    });
    it('status should be clean', () => {
      const status = helper.command.statusJson();
      expect(status.modifiedComponents).to.have.lengthOf(0);
      helper.command.expectStatusToBeClean();
    });
    it('should export tagged components to remote', () => {
      const list = helper.command.listRemoteScopeParsed();
      const comp1 = list.find((comp) => comp.id.includes('comp1'));
      expect(comp1?.localVersion).to.equal('0.0.2');
    });
    it('should delete the remote lane', () => {
      const remoteLanes = helper.command.listRemoteLanesParsed();
      expect(remoteLanes.lanes).to.have.lengthOf(0);
    });
  });

  describe('bit ci merge after lane import --branch', () => {
    let mergeOutput: string;
    before(() => {
      helper.scopeHelper.setWorkspaceWithRemoteScope();
      helper.fixtures.populateComponents(1);
      helper.command.createLane('test-merge-lane');
      helper.command.snapAllComponentsWithoutBuild();
      helper.command.export();

      helper.scopeHelper.reInitWorkspace();
      helper.scopeHelper.addRemoteScope();
      setupGitRemote();
      helper.fs.outputFile('.gitignore', 'node_modules/\n.bit/\n');
      // Get the current branch name (could be main or master depending on git version)
      const mainBranch = helper.command.runCmd('git branch --show-current').trim();
      helper.command.runCmd('git add .');
      helper.command.runCmd('git commit -m "initial commit"');

      helper.command.importLane('test-merge-lane', '--branch -x');
      const branchName = `${helper.scopes.remote}/test-merge-lane`;
      helper.fs.outputFile(`${helper.scopes.remote}/comp1/comp1.js`, 'console.log("merge test");');
      helper.command.runCmd('git commit -am "fix: component update for merge"');
      helper.command.runCmd(`git push -u origin ${branchName}`);

      helper.command.runCmd('bit ci pr');
      helper.command.runCmd(`git checkout ${mainBranch}`);
      helper.command.runCmd(`git merge ${branchName}`);
      helper.command.runCmd(`git push origin ${mainBranch}`);

      mergeOutput = helper.command.runCmd('bit ci merge');
    });
    it('should tag the components that were created on the lane', () => {
      expect(mergeOutput).not.to.include('No components to tag');

      const list = helper.command.listParsed();
      const comp1 = list.find((comp) => comp.id.includes('comp1'));
      expect(comp1).to.exist;
      expect(comp1?.currentVersion).to.equal('0.0.1');
    });
  });

  describe('bit ci merge with workspace.jsonc conflicts due to dependency version changes', () => {
    before(() => {
      helper.scopeHelper.setWorkspaceWithRemoteScope();
      setupGitRemote();

      // Create a fake npm package that we'll use as a dependency
      helper.npm.addFakeNpmPackage('is-positive', '1.0.0');

      // Create comp-a with a dependency on the fake package
      helper.fs.outputFile(
        'comp-a/comp-a.js',
        `const isPositive = require('is-positive');
module.exports = { isPositive };`
      );
      helper.command.addComponent('comp-a');

      helper.workspaceJsonc.addPolicyToDependencyResolver({ dependencies: { 'is-positive': '1.0.0' } });

      // Create .gitignore and initial commit
      helper.fs.outputFile('.gitignore', 'node_modules/\n.bit/\n');
      helper.command.runCmd('git add .');
      helper.command.runCmd('git commit -m "initial commit with is-positive@1.0.0"');
      const defaultBranch = helper.command.runCmd('git branch --show-current').trim();
      helper.command.runCmd(`git push -u origin ${defaultBranch}`);

      helper.command.tagAllWithoutBuild();
      helper.command.export();

      helper.command.runCmd(`git push origin ${defaultBranch}`);

      // Clone the workspace to simulate a parallel development scenario
      const workspaceBeforeDivergence = helper.scopeHelper.cloneWorkspace();

      // Bump the dependency version and tag/export again
      helper.npm.addFakeNpmPackage('is-positive', '2.0.0');
      helper.workspaceJsonc.addPolicyToDependencyResolver({ dependencies: { 'is-positive': '2.0.0' } });

      // Update workspace.jsonc with new version
      const wsConfigUpdated = helper.workspaceJsonc.read();
      wsConfigUpdated['teambit.dependencies/dependency-resolver'].policy.dependencies['is-positive'] = '2.0.0';
      helper.workspaceJsonc.write(wsConfigUpdated);

      helper.command.tagAllWithoutBuild('--unmodified');
      helper.command.export();

      // Go back to the cloned workspace (still on 1.0.0)
      helper.scopeHelper.getClonedWorkspace(workspaceBeforeDivergence);
    });

    it('should fail with clear error message about workspace.jsonc conflicts', () => {
      let error: Error | undefined;
      try {
        helper.command.runCmd('bit ci merge --auto-merge-resolve manual');
      } catch (err: any) {
        error = err;
      }

      expect(error).to.exist;
      expect(error?.message).to.include('workspace.jsonc conflicts');
      expect(error?.message).to.include('bit checkout head');
    });
  });
});
